/*
	Pulsar engine. Core.
	Filename functions

	Copyright (C) 2013-2014 Dmitry Dolgov

	This program is free software; you can redistribute it and/or modify it under the terms of the
	GNU General Public License as published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
	without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
	See the GNU General Public License for more details.
*/

#include <pulsar/pulsar.h>

namespace pl
{

#if defined(PL_WINDOWS) || defined(PL_WINDOWS64)
const pl_char _defaultDelimiter = '\\';
#elif defined(PL_LINUX) || defined(PL_LINUX64)
const pl_char _defaultDelimiter = '/';
#else
#error "Undefined platform"
#endif

/*
	Check delimiter used in the current filename
	Internal function
*/
static pl_char _detectDelimiter(const Filepath &path)
{
	int i;

	for (i=0; path[i]; i++)
	{
		if (isPathDelimiter(path[i]))
		{
			return path[i];
		}
	}
	return _defaultDelimiter;
}

/*
	Find next delimiter in filename
	Internal function
*/
static pl_uint32 _findNextDelimiter(const Filepath &path, pl_uint32 pos)
{
	while (pos < path.size())
	{
		if (isPathDelimiter(path[pos]))
		{
			break;
		}
		pos++;
	}
	return pos;
}

} // namespace pl

/*
	Concatenate path and filename
	Function also insert (if need) or remove extra delimiter between path and fn
	If fn starts with ".\\", this sequence will be ignored
	If fn starts with "..\\", one folder will be removed from path

*/
pl::Filepath pl::mergeFilename(const Filepath &path, const Filepath &fn)
{
	if (path.empty())
	{
		return fn;
	}
	if (fn.empty())
	{
		return path;
	}

	bool bQuoted1 = false;
	bool bQuoted2 = false;
	pl::Filepath pathCopy;
	pl::Filepath fnCopy;

	if (path.size() > 2 && path.isBegin(PL_T("\"")) && path.isEnd(PL_T("\"")))
	{
		bQuoted1 = true;
		pathCopy = pl::canonizeFilename(path, pl::CANONIZE_OPTIONS_DEQUOTA_SPACE);
	}
	else
	{
		pathCopy = path;
	}
	if (fn.size() > 2 && fn.isBegin(PL_T("\"")) && fn.isEnd(PL_T("\"")))
	{
		bQuoted2 = true;
		fnCopy = pl::canonizeFilename(fn, pl::CANONIZE_OPTIONS_DEQUOTA_SPACE);
	}
	else
	{
		fnCopy = fn;
	}

	if (fnCopy.size() > 0 && fnCopy[0] == PL_T('.') && isPathDelimiter(fnCopy[1]))
	{
		if (bQuoted2)
		{
			fnCopy[1] = PL_T('"');
			fnCopy << PL_T('"');
			return mergeFilename(path, fnCopy.get() + 1);
		}
		return mergeFilename(path, fnCopy.get() + 2);
	}

	if (fnCopy.size() >= 2 && fnCopy[0] == PL_T('.') && fnCopy[1] == PL_T('.') && isPathDelimiter(fnCopy[2]))
	{
		if (!pathCopy.empty())
		{
			pathCopy = removeFilename(path);
			if (bQuoted2)
			{
				fnCopy[2] = PL_T('"');
				fnCopy << PL_T('"');
				return mergeFilename(pathCopy, fnCopy.get() + 2);
			}
			return mergeFilename(pathCopy, fnCopy.get() + 3);
		}
	}

	Filepath ret;

	if (isPathDelimiter(pathCopy[pathCopy.size() - 1]) && isPathDelimiter(fnCopy[0]))
	{
		if (bQuoted1 || bQuoted2)
		{
			ret << PL_T('"') << pathCopy;
			ret << fnCopy.get(1) << PL_T('"');
		}
		else
		{
			ret = pathCopy;
			ret << fnCopy.get(1);
		}
		return ret;
	}

	if (!isPathDelimiter(pathCopy[pathCopy.size() - 1]) && !isPathDelimiter(fnCopy[0]))
	{
		pl_char usedDelimiter;
		usedDelimiter = _detectDelimiter(path);
		if (usedDelimiter == '\0')
		{
			usedDelimiter = _detectDelimiter(fn);
		}
		if (bQuoted1 || bQuoted2)
		{
			ret = PL_T('"') + pathCopy + usedDelimiter + fnCopy + PL_T('"');
		}
		else
		{
			ret = pathCopy + usedDelimiter + fnCopy;
		}
		return ret;
	}

	if (bQuoted1 || bQuoted2)
	{
		ret = PL_T('"') + pathCopy + fnCopy + PL_T('"');
	}
	else
	{
		ret = pathCopy + fnCopy;
	}
	return ret;
}

/*
	Remove filename from full path (keep path to file only)
*/
pl::Filepath pl::removeFilename(const Filepath &fn)
{
	if (fn.empty())
	{
		PL_ASSERTW(FALSE, PL_T("Wrong arguments"));
		return fn;
	}

	pl_uint32 i = fn.size();
	bool bQuoted = false;
	Filepath res;

	if (i > 2 && fn[0] == PL_T('"') && fn[i-1] == PL_T('"'))
	{
		bQuoted = true;
		i--;
	}

	while (i > 0 && !isPathDelimiter(fn[i]))
	{
		--i;
	}
	if (pl_strcmp(fn.get() + i+1, PL_T(".")) == 0)
	{
		res = Filepath(fn.get(), i);
		if (bQuoted)
		{
			res << PL_T('"');
		}
		return removeFilename(res);
	}
	if (isPathDelimiter(fn[i]) && i == fn.size()-1)
	{
		res = Filepath(fn.get(), i);
		if (bQuoted)
		{
			res << PL_T('"');
		}
		return removeFilename(res);
	}
	if (pl_strcmp(fn.get() + i+1, PL_T("..")) == 0)
	{
		res = Filepath(fn.get(), i);
		if (bQuoted)
		{
			res << PL_T('"');
		}
		return removeFilename(removeFilename(res));
	}

	res = Filepath(fn.get(), i);

	if(res.size() > 0 && res[res.size() - 1] == PL_T(':'))
	{
		res += _defaultDelimiter;
	}

	if (bQuoted)
	{
		res << PL_T('"');
	}
	return res;
}

/*
	Remove last extension from filename
*/
pl::Filepath pl::removeExtension(const Filepath &fn)
{
	pl_uint32 i = fn.size();
	while (i > 0 && !isPathDelimiter(fn[i]))
	{
		if (fn[i] == PL_T('.'))
		{
			return Filepath(fn.get(), i);
		}
		--i;
	}
	return fn;
}

/*
	Replace one last extension with another
*/
pl::Filepath pl::replaceExtension(const Filepath &fn, const pl_char* extNew)
{
	Filepath fnNew = removeExtension(fn);

	if (extNew == NULL)
	{
		return fnNew;
	}
	if (extNew[0] == PL_T('.'))
	{
		extNew++;
	}

	if (fnNew.size() > 0 && fnNew[fnNew.size() - 1] == PL_T('.'))
	{
		fnNew << extNew;
	}
	else
	{
		fnNew << PL_T('.') << extNew;
	}
	return fnNew;
}

/*
	Get filename from full path
*/
pl::Filepath pl::extractFilename(const Filepath &fn)
{
	pl_uint32 i = fn.size();
	while (i > 0 && !isPathDelimiter(fn[i - 1]))
	{
		--i;
	}
	return Filepath(fn.get() + i);
}

/**
	Get last extension from filename
	If fn end with \\ or if file name does not contain extension, empty string will be removed
*/
pl::Filepath pl::extractExtension(const Filepath &fn)
{
	Filepath ext;
	if (fn.size() != 0)
	{
		for (pl_uint32 i = fn.size(); i > 0; --i)
		{
			pl_char c = fn[i];

			if (isPathDelimiter(c))
			{
				break;
			}

			if (c == PL_T('.'))
			{
				ext.set(fn.get() + i + 1);
				break;
			}
		}
	}
	return ext;
}

/*
	Extract drive name as *:\ or \\server\share
*/
pl::Filepath pl::extractDrive(const Filepath &fn)
{
	pl_uint32 pos, startPos = 0;
	pl::Filepath res;

	if (fn.empty())
	{
		return fn;
	}
	if (fn[0] == PL_T('"'))
	{
		startPos++;
	}
	if (isPathDelimiter(fn[startPos]) && isPathDelimiter(fn[startPos+1]))
	{
		// \\server\share
		pos = _findNextDelimiter(fn, startPos+2);
		if (pos < fn.size())
		{
			pos = _findNextDelimiter(fn, pos+1);
		}
		res = fn;
		res.truncate(pos);
		return res;
	}

	// "*:\"
	for (pos=startPos; pos < fn.size(); pos++)
	{
		if (pos > startPos && fn[pos] == PL_T(':') && isPathDelimiter(fn[pos+1]))
		{
			res.set(fn.get(startPos), pos+2 - startPos);
			return res;
		}
	}
	return fn;
}

/**
	Try to make relative path
*/
bool pl::makeRelativePath(const Filepath &path, const Filepath &absName, Filepath &relName)
{
	pl_uint32 curPosPath = 0;
	pl_uint32 curPosAbsName = 0;
	pl_uint32 nextPosPath;
	pl_uint32 nextPosAbsName;

	while (curPosPath < path.size() && curPosAbsName < absName.size())
	{
		nextPosPath = _findNextDelimiter(path, curPosPath+1);
		nextPosAbsName  = _findNextDelimiter(absName, curPosAbsName+1);

		if (nextPosPath != nextPosAbsName ||
		    pl_strnicmp(path.get(), absName.get(), nextPosPath) != 0)
		{
			if (curPosPath == 0)
			{
				PL_ASSERTW(curPosAbsName == 0, PL_T("Internal error"));
				return false;
			}
			break;
		}

		curPosPath = nextPosPath;
		curPosAbsName = nextPosAbsName;
	}

	relName.clear();

	while (curPosPath < path.size())
	{
		if (curPosPath == path.size()-1 && isPathDelimiter(path[curPosPath]))
		{
			break;
		}

		relName << PL_T("..\\");
		curPosPath = _findNextDelimiter(path, curPosPath+1);
	}

	if (curPosAbsName < absName.size())
	{
		curPosAbsName++;
	}

	if (relName.empty())
	{
		relName.set(PL_T(".\\"));
	}
	relName = mergeFilename(relName, absName.get()+curPosAbsName);
	PL_ASSERTE(isEqualFilename(pl::mergeFilename(path, relName), absName, pl::CANONIZE_OPTIONS_LOWERCASE | pl::CANONIZE_OPTIONS_SLASH), PL_T("Internal function error"));
	return true;
}

/**
	Canonize filename according options
*/
pl::Filepath pl::canonizeFilename(const Filepath &fn, pl_uint32 options)
{
	PL_ASSERTW((options & (CANONIZE_OPTIONS_SLASH | CANONIZE_OPTIONS_BACKSLASH)) !=
	          (CANONIZE_OPTIONS_SLASH | CANONIZE_OPTIONS_BACKSLASH), PL_T("Wrong options"));
	PL_ASSERTW((options & (CANONIZE_OPTIONS_LOWERCASE | CANONIZE_OPTIONS_UPPERCASE)) !=
	          (CANONIZE_OPTIONS_LOWERCASE | CANONIZE_OPTIONS_UPPERCASE), PL_T("Wrong options"));

	pl::Filepath outputPath;
	pl_char srcBuffer[MAX_PATH+5] = {0};
	pl_uint32 i;

	if (options & (CANONIZE_OPTIONS_DEQUOTA_SPACE | CANONIZE_OPTIONS_ISO9660))
	{
		if (fn.size() > 1 && fn[0] == PL_T('\"') && fn[fn.size()-1] == PL_T('\"'))
		{
			pl_strcpy(srcBuffer, fn.get()+1);
			srcBuffer[fn.size()-2] = PL_T('\0');
		}
		else
		{
			pl_strcpy(srcBuffer, fn.get());
		}
	}
	else
	{
		pl_strcpy(srcBuffer, fn.get());
	}

	if (options & CANONIZE_REMOVE_SUBDOTS)
	{
		for (i=0; srcBuffer[i] != PL_T('\0'); i++)
		{
			if (isPathDelimiter(srcBuffer[i]) && srcBuffer[i+1] == PL_T('.'))
			{
				if (isPathDelimiter(srcBuffer[i+2]))
				{
					pl_strcpy(srcBuffer+i, srcBuffer+i+2);
				}
				else if (srcBuffer[i+2] == PL_T('.') && isPathDelimiter(srcBuffer[i+3]))
				{
					outputPath = pl::removeFilename(outputPath);
					pl_strcpy(srcBuffer+i, srcBuffer+i+3);
				}
			}
			outputPath << srcBuffer[i];
		}
	}
	else
	{
		outputPath.set(srcBuffer);
	}

	if (options & CANONIZE_OPTIONS_UPPERCASE)
	{
		for (i=0; i<outputPath.size(); i++)
		{
			outputPath[i] = pl_toupper(outputPath[i]);
		}
	}
	else if (options & CANONIZE_OPTIONS_LOWERCASE)
	{
		for (i=0; i<outputPath.size(); i++)
		{
			outputPath[i] = pl_tolower(outputPath[i]);
		}
	}
	if (options & CANONIZE_OPTIONS_SLASH)
	{
		outputPath.replaceChar(PL_T('\\'), PL_T('/'));
	}
	else if (options & CANONIZE_OPTIONS_BACKSLASH)
	{
		outputPath.replaceChar(PL_T('/'), PL_T('\\'));
	}
	if ((options & CANONIZE_OPTIONS_ISO9660) && outputPath.size() > 1)
	{
		if (outputPath[outputPath.size()-1] != PL_T('1') || outputPath[outputPath.size()-2] != PL_T(';'))
		{
			outputPath << PL_T(";1");
		}
	}
	if ((options & CANONIZE_OPTIONS_QUOTA_SPACE) && pl_strchr(outputPath.get(), PL_T(' ')) != NULL)
	{
		if (outputPath[0] != PL_T('\"'))
		{
			pl::Filepath tempPath = outputPath;
			outputPath << PL_T('\"') << tempPath << PL_T('\"');
		}
	}
	return outputPath;
}

/**
	Compare file names
*/
bool pl::isEqualFilename(const Filepath &fn1, const Filepath &fn2, pl_uint32 options /*= CANONIZE_OPTIONS_SLASH*/)
{
	pl::Filepath name1 = canonizeFilename(fn1, options);
	pl::Filepath name2 = canonizeFilename(fn2, options);
	return name1 == name2;
}

/**
	Calculate total amount of delimiters
*/
int pl::countDelimiter(const Filepath &name)
{
	int count = 0;
	pl_uint32 i;

	for (i=0; i<name.size(); i++)
	{
		if (isPathDelimiter(name[i]))
		{
			count++;
		}
	}
	return count;
}
